# plugs/rss.py
#
#

""" manage rss feeds """

__copyright__ = 'this file is in the public domain'

from ommbot.world import rlog, get_fleet, add_cmnd, add_example, get_datadir
from ommbot.persist import Persist
from ommbot.generic import geturl, handle_exception
from ommbot.statdict import Statdict
import ommbot.thr
from xml.sax.handler import ContentHandler
import xml.sax
import re
import time

fleet = get_fleet()
datadir = get_datadir()

class Rsshandler(ContentHandler):

    """ the rss sax handler """

    def __init__(self):
        ContentHandler.__init__(self)
        self.l = []
        self.result = []

    def startElement(self, name, attrs):
        """ start of element """
        self._txt = ""

    def characters(self, data):
        """ char data """
        self._txt += data.replace('\n',' ')

    def endElement(self, name):
        """ end of element """
        self.result.append((name, self._txt.replace('  ', ' ')))

class Rssitem(object):

    """ item that contains rss data """

    def __init__(self, name, url, itemslist, watchchannels=[], \
sleeptime=30*60):
        self.name = name
        self.url = url
        self.itemslist = list(itemslist)
        self.watchchannels = list(watchchannels)
        self.sleeptime = int(sleeptime)
        self.running = 0
        self.stoprunning = 0
        self.botname = None

    def __str__(self):
        return "name=%s url=%s itemslist=%s watchchannels=%s sleeptime=%s \
running=%s" % (self.name, self.url, str(self.itemslist), \
str(self.watchchannels), str(self.sleeptime), self.running)

class Rssdict(Persist):

    """ dict of rss entries """

    def __init__(self, filename):
        Persist.__init__(self, filename)
        if not self.data:
            self.data = {}
        self.handlers = {}
        for i in self.data.values():
            self.handlers[i.name] = Rsshandler()

    def add(self, name, url):
        """ add rss item """
        rlog(10, 'rss', 'adding %s %s' % (name, url))
        self.data[name] = Rssitem(name, url, ['title', ])
        self.handlers[name] = Rsshandler()
        self.save()

    def delete(self, name):
        """ delete rss item by name """
        for i in self.data.values():
            if i.name == name:
                i.running = 0
                del self.data[name]
                self.save()
 
    def byname(self, name):
        """ return rss item by name """
        for i in self.data.values():
            if i.name == name:
                return i

    def getdata(self, name):
        """ get data of rss feed """
        rssitem = self.byname(name)
        if rssitem == None:
            return []
        # fetch data from url
        try:
            urlresult = geturl(rssitem.url)
        except Exception, ex:
            rlog(10, 'rss', str(ex))
            raise
        if urlresult == None:
            return []
        # try to parse data with sax
        try:
            xml.sax.parseString(urlresult, self.handlers[rssitem.name])
            result = self.handlers[rssitem.name].result
        except Exception, ex:
            rlog(10, 'rss', "can't parse %s feed" % rssitem.name)
            self.handlers[rssitem.name] = Rsshandler()
            self.handlers[rssitem.name].result = []
            return []
        # result is cached
        self.handlers[rssitem.name].result = []
        first = rssitem.itemslist[0]
        temp1 = []
        temp2 = []
        nritems = len(rssitem.itemslist)
        # loop over result and return list of itemlist items
        for i in result:
            if i[0] == first:
                temp1 = []
                temp = i[1].strip()
                temp1.append(temp)
                continue
            if i[0] in rssitem.itemslist:
                temp = i[1].strip()
                temp1.append(temp)
            if len(temp1) == nritems:
                temp2.append(temp1)
                temp1 = []
        return temp2

class Rsswatcher(Rssdict):

    """ rss watchers """ 

    def __init__(self, filename):
        Rssdict.__init__(self, filename)
        self.results = {}

    def startwatchers(self):
        """ start watcher threads """
        for i in self.data.values():
            if i.running:
                rlog(10, 'rss', 'starting %s rss watch' % i.name)
                ommbot.thr.start_new_thread(self.watch, (i.name, ))

    def stopwatchers(self):
        """ stop all watcher threads """
        for i in self.data.values():
            if i.running:
                i.stoprunning = 1

    def watch(self, name):
        """ start a watcher thread """
        length = 0
        # get basic data
        try:
            result = self.getdata(name)
            self.results[name] = result
            rssitem = self.byname(name)
            rssitem.running = 1
            rssitem.stoprunning = 0
            # keep old itemslist to see if it has changed
            olditemslist = list(rssitem.itemslist)
        except Exception, ex:
            rlog(10, 'rss', 'not starting watch reason: %s' % ex)
            return
        self.save()
        # poll every sleeptime seconds
        while rssitem.running and not rssitem.stoprunning:
            time.sleep(rssitem.sleeptime)
            try:
                if not rssitem.running or rssitem.stoprunning:
                    return
                if olditemslist != rssitem.itemslist:
                    try:
                        res = self.getdata(name)
                        if not res:
                            continue
                    except:
                        continue
                    self.results[name] = res
                    olditemslist = list(rssitem.itemslist)
                    continue
                try:
                    res = self.getdata(name)
                    if not res:
                        rlog(10, 'rss', "can't match %s data" % name)
                        continue
                except:
                    continue
                # loop over result to see if we already seen item
                for j in res:
                    if j not in self.results[name]:
                        self.results[name].append(j)
                    else:
                        continue
                    resultstr = ""
                    for item in j:
                        resultstr += "%s - " % item
                    resultstr = resultstr[:-3]
                    if not resultstr:
                        continue
                    # output new entry to watchchannels
                    for item in rssitem.watchchannels:
                        try:
                            (botname, channel) = item
                            bot = fleet.byname(botname)
                            if not bot:
                                rlog(10, 'rss', "can't find bot %s in fleet" \
% botname)
                                continue
                        except:
                            rlog(10, 'rss', '%s is not in the format \
(botname,channel)' % item)
                            continue
                        time.sleep(5)
                        try:
                            bot.say(channel, "%s: %s" % (rssitem.name, \
resultstr))
                        except:
                            handle_exception()
            except Exception, ex:
                handle_exception()

    def stopwatch(self, name):
        """ stop watcher thread """
        for i, j in self.data.iteritems():
            if i == name:
                j.running = 0
                try:
                    del self.results[name]
                except KeyError:
                    pass
                self.save()
                return 1

    def list(self):
        """ return of rss names """
        return self.data.keys()

    def runners(self):
        """ show names/channels of running watchers """
        result = []
        for i in self.data.values():
            if i.running == 1: 
                result.append((i.name, i.watchchannels))
        return result

    def url(self, name):
        """ return url of rssitem """
        for i in self.data.values():
            if i.name == name:
                return i.url

    def scan(self, name):
        """ scan a rss url for used xml items """
        try:
            result = geturl(self.url(name))
        except Exception, ex:
            rlog(10, 'rss', str(ex))
            return None
        statdict = Statdict()
        result = result.replace('\n',' ')
        all = re.findall('</*(.*?)>', result)
        for i in all:
            if i.find('?') != -1 or i.find('http') != -1:
                continue
            statdict.upitem(i)
        return statdict.top()

watcher = Rsswatcher(datadir + '/rss')

def init(): 
    """ called after plugin import """
    ommbot.thr.start_new_thread(watcher.startwatchers, ())
    return 1
    
def shutdown():
    """ called before plugin import """
    watcher.stopwatchers()
    return 1
    
def handle_rssadd(bot, ievent):
    """ rss-add <name> <url> .. add a rss item """
    try:
        (name, url) = ievent.args
    except ValueError:
        ievent.missing('<name> <url>')
        return
    watcher.add(name, url)
    ievent.reply('rss item added')

add_cmnd('rss-add', handle_rssadd, ['RSS', 'OPER'])
add_example('rss-add', 'rss-add <name> <url> to the rsswatcher','rss-add \
             slashdot http://slashdot.org/slashdot.xml')

def handle_rssdel(bot, ievent):
    """ rss-del <name> .. delete a rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    watcher.delete(name)
    ievent.reply('rss item deleted')

add_cmnd('rss-del', handle_rssdel, ['RSS', 'OPER'])
add_example('rss-del', 'rss-del <name> .. remove <name> from the \
             rsswatcher', 'rss-del slashdot')

def handle_rsswatch(bot, ievent):
    """ rss-watch <name> .. start watcher thread """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    if not rssitem.running:
        if not ievent.channel:
            ievent.reply('no channel provided')
        elif (bot.name,ievent.channel) not in rssitem.watchchannels:
            rssitem.watchchannels.append((bot.name, ievent.channel))
        ommbot.thr.start_new_thread(watcher.watch, (name, ))
        ievent.reply('watcher thread started')
    else:
        ievent.reply('already watching %s' % name)

add_cmnd('rss-watch', handle_rsswatch, ['RSS', 'OPER'])
add_example('rss-watch', 'rss-watch <name> .. go watching <name>', \
            'rss-watch slashdot')

def handle_rsschannels(bot, ievent):
    """ rss-channels <name> .. show channels of rss feed """
    try:
        (name, ) = ievent.args
    except ValueError:
        ievent.missing("<name>") 
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    ievent.reply(rssitem.watchchannels)

add_cmnd('rss-channels', handle_rsschannels, ['RSS', 'OPER'])
add_example('rss-channels', 'rss-channels <name> .. show channels', \
            'rss-channels slashdot')

def handle_rssaddchannel(bot, ievent):
    """ rss-addchannel <name> [<botname>] <channel> .. add a channel to \
        rss item """
    botname = None
    try:
        (name, botname, channel) = ievent.args
    except ValueError:
        try:
            (name, channel) = ievent.args
        except ValueError:
            ievent.missing('<name> [<botname>] <channel>')
            return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    if not botname:
        if channel in rssitem.watchchannels:
            ievent.reply('we are already monitoring %s on %s' % \
(name, ievent.channel))
            return
    else:
        if (botname, channel) in rssitem.watchchannels:
            ievent.reply('we are already monitoring %s on (%s,%s)' % \
(name, botname, ievent.channel))
            return
    if not botname:
        rssitem.watchchannels.append(channel)
    else:
        rssitem.watchchannels.append((botname, channel))
    watcher.save()
    ievent.reply('rss channel added')

add_cmnd('rss-addchannel', handle_rssaddchannel, ['RSS', 'OPER'])
add_example('rss-addchannel', 'rss-addchannel <name> [<botname>] <channel> \
             ..add <channel> or <botname> <channel> to watchchannels of \
             <name>', '1) rss-addchannel slashdot #dunkbots 2) \
             rss-addchannel slashdot main #dunkbots')

def handle_rssdelchannel(bot, ievent):
    """ rss-delchannel <name> [<botname>] <channel> .. delete channel \
        from rss item """
    botname = None
    try:
        (name, botname, channel) = ievent.args
    except ValueError:
        try:
            (name, channel) = ievent.args
            botname = None
        except ValueError:
            ievent.missing('<name> [<botname>] <channel>')
            return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    if not botname:
        if  channel not in rssitem.watchchannels:
            ievent.reply('we are not monitoring %s on %s' % \
(name, ievent.channel))
            return
    else:
        if (botname, channel) not in rssitem.watchchannels:
            ievent.reply('we are not monitoring %s on (%s,%s)' % \
(name, botname, ievent.channel))
            return
    if not botname:
        rssitem.watchchannels.remove(channel)
        ievent.reply('rss channel deleted')
    else:
        rssitem.watchchannels.remove((botname, channel))
        ievent.reply('rss channel deleted')
    watcher.save()

add_cmnd('rss-delchannel', handle_rssdelchannel, ['RSS', 'OPER'])
add_example('rss-delchannel', 'rss-delchannel <name> [<botname>] <channel> \
             .. delete <channel> or <botname> <channel> from watchchannels of \
             <name>', '1) rss-delchannel slashdot #dunkbots 2) rss-delchannel \
             slashdot main #dunkbots')

def handle_rssstopwatch(bot, ievent):
    """ rss-stopwatch <name> .. stop a watcher thread """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    if not watcher.stopwatch(name):
        ievent.reply("can't stop %s watcher" % name)
        return
    ievent.reply('stopped %s rss watch' % name)

add_cmnd('rss-stopwatch', handle_rssstopwatch, ['RSS', 'OPER'])
add_example('rss-stopwatch', 'rss-stopwatch <name> .. stop polling <name>', \
            'rss-stopwatch slashdot')

def handle_rsssleeptime(bot, ievent):
    """ rss-sleeptime <name> .. get sleeptime of rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    try:
        ievent.reply('sleeptime for %s is %s seconds' % (name, \
str(rssitem.sleeptime)))
    except AttributeError:
        ievent.reply("can't get sleeptime for %s" % name)

add_cmnd('rss-sleeptime', handle_rsssleeptime, ['RSS', 'OPER'])
add_example('rss-sleeptime', 'rss-sleeptime <name> .. get sleeping time \
             for <name>', 'rss-sleeptime slashdot')

def handle_rsssetsleeptime(bot, ievent):
    """ rss-setsleeptime <name> <seconds> .. set sleeptime of rss item """
    try:
        (name, sec) = ievent.args
        sec = int(sec)
    except ValueError:
        ievent.missing('<name> <seconds>')
        return
    if sec < 60:
        ievent.reply('min is 60 seconds')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    rssitem.sleeptime = sec
    if rssitem.sleeptime > sec and rssitem.running:
        watcher.stopwatch(name)
        ommbot.thr.start_new_thread(watcher.watch, (name))
    watcher.save()
    rssitem.sleeptime = sec
    ievent.reply('sleeptime set')

add_cmnd('rss-setsleeptime', handle_rsssetsleeptime, ['RSS', 'OPER'])
add_example('rss-setsleeptime', 'rss-setsleeptime <name> <seconds> .. set \
             sleeping time for <name> .. min 60 sec', 'rss-setsleeptime \
             slashdot 600')

def handle_rssget(bot, ievent):
    """ rss-get <name> .. fetch rss data """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    try:
        result = watcher.getdata(name)
    except Exception, ex:
        ievent.reply(str(ex))
        return
    resultstr = ""
    teller = 1
    for i in result:
        tempstr = ""
        for item in i:
            tempstr += "%s - " % item
        tempstr = tempstr[:-3]
        resultstr += "%s) %s " % (teller, tempstr.strip())
        teller += 1
    if resultstr:
        ievent.reply(resultstr)
    else:
        ievent.reply("can't match watcher data")

add_cmnd('rss-get', handle_rssget, ['RSS', 'OPER'])
add_example('rss-get', 'rss-get <name> .. get data from <name>', 'rss-get \
             slashdot')

def handle_rssrunning(bot, ievent):
    """ rss-running .. show which watchers are running """
    result = watcher.runners()
    resultstr = ""
    teller = 1
    for i in result:
        resultstr += "%s) %s %s " % (str(teller), i[0], i[1])
        teller += 1
    if resultstr:
        ievent.reply(resultstr)
    else:
        ievent.reply('nothing running yet')

add_cmnd('rss-running', handle_rssrunning, ['RSS', 'OPER'])
add_example('rss-running', 'rss-running .. get running rsswatchers', \
            'rss-running')

def handle_rsslist(bot, ievent):
    """ rss-list .. return list of rss items """
    result = watcher.list()
    ievent.reply(str(result))

add_cmnd('rss-list', handle_rsslist, ['RSS', 'OPER'])
add_example('rss-list', 'get list of rss items', 'rss-list')

def handle_rssurl(bot, ievent):
    """ rss-url <name> .. return url of rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    result = watcher.url(name)
    ievent.reply(str(result))

add_cmnd('rss-url', handle_rssurl, ['RSS', 'OPER'])
add_example('rss-url', 'rss-url <name> .. get url from rssitem with \
             <name>', 'rss-url slashdot')

def handle_rssadditemslist(bot, ievent):
    """ rss-additemslist <name> <item> .. add item to itemslist of rss item """
    try:
        (name, item) = ievent.args
    except ValueError:
        ievent.missing('<name> <item>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    rssitem.itemslist.append(item)
    watcher.save()
    ievent.reply('item added to itemslist')

add_cmnd('rss-additemslist', handle_rssadditemslist, ['RSS', 'OPER'])
add_example('rss-additemslist', 'rss-additemslist <name> <item> .. add to \
             itemslist of <name> ', 'rss-additemslist slashdot url')

def handle_rssdelitemslist(bot, ievent):
    """ rss-delitemslist <name> <item> .. delete item from itemslist """
    try:
        (name, item) = ievent.args
    except ValueError:
        ievent.missing('<name> <item>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    if item in rssitem.itemslist:
        rssitem.itemslist.remove(item)
        watcher.save()
        ievent.reply('item deleted from itemslist')
    else:
        ievent.reply("%s doesn't have %s in itemslist" % (name, item))

add_cmnd('rss-delitemslist', handle_rssdelitemslist, ['RSS', 'OPER'])
add_example('rss-delitemslist', 'rss-delitemslist <name> <item> .. \
            delete from itemslist of <name> ', 'rss-delitemslist slashdot url')

def handle_rssitemslist(bot, ievent):
    """ rss-itemslist <name> .. show itemslist of rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    ievent.reply(str(rssitem.itemslist))

add_cmnd('rss-itemslist', handle_rssitemslist, ['RSS', 'OPER'])
add_example('rss-itemslist', 'rss-itemslist <name> .. get itemslist of \
             <name> ', 'rss-itemslist slashdot')

def handle_rssscan(bot, ievent):
    """ rss-scan <name> .. scan rss item for used xml items """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    result = watcher.scan(name)
    if result == None:
        ievent.reply("can't get data for %s" % name)
        return
    resultstr = ""
    for i in result:
        resultstr += "%s = %s " % i
    ievent.reply(resultstr)

add_cmnd('rss-scan', handle_rssscan, ['RSS', 'OPER'])
add_example('rss-scan', 'rss-scan <name> .. get items of <name> ', \
            'rss-scan slashdot')

def handle_rsssync(bot, ievent):
    """ rss-sync <name> .. sync rss item data """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    try:
        result = watcher.getdata(name)
        watcher.results[name] = result
        ievent.reply('%s synced' % name)
    except Exception, ex:
        ievent.reply("ERROR: %s" % str(ex))

add_cmnd('rss-sync', handle_rsssync, ['RSS', 'OPER'])
add_example('rss-sync', 'rss-sync <name> .. sync data of <name>', 'rss-sync \
             slashdot')
